from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMessageBox, QFrame, QPushButton, QProgressBar, QWidget, QDesktopWidget, \
    QFileDialog
from PyQt5.QtGui import QFont  # 添加图标
from PyQt5 import QtCore
from PyQt5.QtCore import QObject, pyqtSignal
import time
from threading import Thread
import random
import numpy as np
from abc import ABCMeta
import types
import inspect
import ctypes
import geatpy as ea  # pip install geatpy==2.5.1  需要python3.7
# 自定义函数
# 写成exe时要将自定义和第三方的包放入site-packages文件夹内
from egfunc.ft import ft  # 外包络函数
from egfunc.designrs import designRs  # 设计谱函数
from egfunc.resspec import resspec  # 响应谱函数
from egfunc.hecheng import hecheng  # 地震波合成函数
from egfunc.find_bad_w import find_bad_w  # 找误差最大的频率
from egfunc.glp import fyp2glp  # 功率谱计算函数


class GlobalDataContainer:
    def __init__(self):
        """
        t:    时程曲线 时间序列
        Aw:   幅值谱  三角级数叠加时的各频率分量幅值
        fai:  随机相位
        bl:   外包络曲线
        w:    角频率
        f:    频率  Hz
        index_w: 用于优化的频率处于频率序列中的位置
        Tyz:  反应谱横坐标
        Damp: 阻尼比
        sjp:  设计谱
        N_adjust:用于优化的点数  实际上大约是其2倍   N_adjust个误差最大的频率 + N_adjust个幅值最大的频率
        advancing_iter: 优化次数
        """
        # 只构造引用，不实际赋值
        self.t = None
        self.Aw = None
        self.fai = None
        self.bl = None
        self.w = None
        self.f = None
        self.index_w = None
        self.Tyz = None
        self.Damp = None
        self.sjp = None
        self.N_adjust = None
        self.advancing_iter = None


class SWG:
    """
    Seismic waves generator  主函数类 控制流程运行
    """

    def __init__(self, ui, ui_class, father):  # 初始化
        self.UI_widget = ui
        self.SWG_UI = ui_class
        self.father = father
        self.gdc = GlobalDataContainer()
        # 声明全局变量
        # 从文件中加载UI定义
        # widget = QtWidgets.QWidget()
        # self.SWG_UI= Ui_SWG()
        # self.SWG_UI.setupUi(widget)
        # self.SWG_UI = uic.loadUi(r'.\source\UI\SWG.ui')
        self.init_UI()
        "================ 默认地震参数设置====================="
        self.cd = 0  # 场地类型 0~4
        self.ld = 0  # 地震烈度 6 7 7.5 8 8.25 8.5 9 10 对应0-7
        self.fz = 1  # 场地分组 1~3
        self.rare = 'basic'  # 0多遇地震 1罕遇地震  影响特征周期
        self.Td = 20  # 人造地震动持续时间
        self.g = 9.8  # 重力加速度
        self.gdc.Damp = 0.05  # 阻尼比

        "=========构造反应谱横坐标序列========="
        f1 = np.arange(0.15, 3, 0.05)
        f2 = np.arange(3, 3.6, 0.15)
        f3 = np.arange(3.6, 5, 0.2)
        f4 = np.arange(5, 8, 0.25)
        f5 = np.arange(8, 15, 0.5)
        f6 = np.arange(15, 34, 1)
        ff = np.concatenate((f1, f2, f3, f4, f5, f6))
        # 反应谱频率范围：0.15-33Hz
        Tyz = np.sort(1. / ff)  # 反应谱横坐标  非均匀
        Tyz = np.insert(Tyz, 0, 0, 0)  # 增加零点
        self.nTyz = len(Tyz)  # 特征周期点个数
        self.gdc.Tyz = Tyz
        "========构造三角级数频率序列=========="
        # 频率点数需要比上面的反应谱横坐标构造时采用的频率点数多
        self.delta_f = 0.2  # 频率间隔
        f = np.arange(0.1, 33 + self.delta_f, self.delta_f)
        f = np.round(f, 3)  # 保留三位小数  double类型精度会有误差  末位会不对 如0.3000000004
        self.gdc.f = f
        w = np.pi * 2 * f  # 角频率
        self.gdc.w = w
        self.npl = len(w)  # 频率点数

        "========初始化相位序列=========="
        self.gdc.fai = np.zeros(self.npl)
        "========初始化时间序列=========="
        self.gdc.t = np.arange(0, self.Td + 0.01, 0.01)  # 人造地震动时间序列
        self.nt = len(self.gdc.t)
        "========初始化外包络线=========="
        self.gdc.bl = np.zeros(self.nt)
        "========初始化计算结果容器=========="
        self.at = np.zeros_like(self.gdc.t)  # at表示地震波
        self.MAcc = np.zeros(self.nTyz)  # MAcc表示反应谱
        self.error = np.zeros(self.nTyz)  # error 表示误差曲线
        self.SW = np.zeros(self.npl)  # 设计功率谱
        # self.wave_SW = np.zeros(self.npl) # 实际功率谱

        "========重要运行参数初始化=========="
        self.gdc.advancing_iter = 10
        self.gdc.N_adjust = 5
        self.rc = 0.2  # 直接迭代的目标最大误差
        self.max_iter = 20  # 简单迭代的最大迭代次数

        "================计算过程状态机=============="
        self.progress_machine = 0  # 初始化为0，生成初始曲线后为1,简单迭代后为2, 优化后为3

        "================画布初始化==============="
        self.init_graph()

        "===============实例化动作信号==============="
        self.ms = MySignals()

        "================优化算法选择==================="
        self.advancing_algorithm = '遗传算法'

    def init_UI(self):
        """
        UI界面初始化
        :return:
        """
        "============禁止缩放窗口============="
        # self.UI_widget.setMaximumSize(1200, 800)
        # self.UI_widget.setMinimumSize(1200, 800)
        self.UI_widget.setFixedSize(1200,800)
        # self.UI_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.UI_widget.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint)

        "===================将UI组件与函数连接=========================="
        "=======tab1======="
        self.SWG_UI.pushButton_begin.clicked.connect(self.handle_begin)  # 开始按钮和生成初始地震波的函数相连
        self.SWG_UI.pushButton_exit.clicked.connect(self.quit_program)  # 退出按钮和退出函数相连
        self.SWG_UI.pushButton_easy_iter.clicked.connect(self.handle_easy_iter)  # 简单迭代按钮
        self.SWG_UI.pushButton_output.clicked.connect(self.output)  # 导出数据按钮
        self.SWG_UI.pushButton_advancing_begin.clicked.connect(self.handle_advanced_algorithm)  # 遗传算法优化按钮

        self.SWG_UI.comboBox_cd.currentIndexChanged[str].connect(self.set_cd)  # 场地类型按钮
        self.SWG_UI.comboBox_ld.currentIndexChanged[str].connect(self.set_ld)  # 烈度按钮
        self.SWG_UI.comboBox_fz.currentIndexChanged[str].connect(self.set_fz)  # 分组按钮
        self.SWG_UI.comboBox_rare.currentIndexChanged[str].connect(self.set_rare)  # 罕见度按钮

        self.SWG_UI.lineEdit_Damp.editingFinished.connect(self.set_Damp)  # 阻尼比设置框
        self.SWG_UI.lineEdit_Td.editingFinished.connect(self.set_Td)  # 地震持时设置框
        self.SWG_UI.lineEdit_rc.editingFinished.connect(self.set_rc)  # 最大误差设置框
        self.SWG_UI.lineEdit_max_iter.editingFinished.connect(self.set_max_iter)  # 最大迭代次数设置框
        self.SWG_UI.lineEdit_N_adjust.editingFinished.connect(self.set_N_adjust)  # 遗传算法调整点数设置
        self.SWG_UI.lineEdit_advancing_iter.editingFinished.connect(self.set_advancing_iter)  # 遗传算法迭代次数设置

        # 算法选择按钮组
        self.SWG_UI.comboBox_algorithm.currentIndexChanged[str].connect(self.select_advancing_algorithm)
        # 箭头初始化
        self.set_arrow(1)
        # 退出按钮美化
        self.SWG_UI.pushButton_exit.setStyleSheet("QPushButton{font: 20pt '楷体'}"
                                                  "QPushButton{color:black}"
                                                  "QPushButton:hover{color:red}"
                                                  "QPushButton{background-color:rgb(233,255,211)}"
                                                  "QPushButton{border:2px}"
                                                  "QPushButton{border-radius:10px}"
                                                  "QPushButton{padding:2px 4px}")

        for i in [self.SWG_UI.lineEdit_Td, self.SWG_UI.lineEdit_rc, self.SWG_UI.lineEdit_advancing_iter,
                  self.SWG_UI.lineEdit_max_iter, self.SWG_UI.lineEdit_N_adjust, self.SWG_UI.lineEdit_Damp]:
            i.setStyleSheet("QLineEdit{background-color: rgb(255,255,255)}"
                            "QLineEdit:focus{background-color: rgb(255,252,211)}")

    def init_graph(self):
        """
        画布初始化
        """
        # 反应谱画布
        self.SWG_UI.graph_fyp.setBackground('w')
        self.SWG_UI.graph_fyp.setTitle("加速度反应谱", color='000080', size='18pt')
        self.SWG_UI.graph_fyp.setLabel("left", "加速度(m/s^2)", color='b', font='16pt')
        self.SWG_UI.graph_fyp.setLabel("bottom", "周期(s)", color='b', font='16pt')
        font = QFont()
        font.setFamily("Times")
        font.setPointSize(12)
        self.SWG_UI.graph_fyp.getAxis("left").setStyle(tickFont=font, tickTextOffset=10)
        self.SWG_UI.graph_fyp.getAxis("bottom").setStyle(tickFont=font, tickTextOffset=10)
        self.SWG_UI.graph_fyp.enableAutoRange('xy', True)
        self.SWG_UI.graph_fyp.showGrid(True, True)

        # 时程曲线画布
        self.SWG_UI.graph_wave.setBackground('w')
        self.SWG_UI.graph_wave.setTitle("地震波时程曲线", color='000080', size='18pt')
        self.SWG_UI.graph_wave.setLabel("left", "加速度(m/s^2)", font='16pt')
        self.SWG_UI.graph_wave.setLabel("bottom", "时间(s)", color='b', font='16pt')
        self.SWG_UI.graph_wave.getAxis("left").setStyle(tickFont=font, tickTextOffset=10)
        self.SWG_UI.graph_wave.getAxis("bottom").setStyle(tickFont=font, tickTextOffset=10)

        self.SWG_UI.graph_wave.enableAutoRange('xy', True)
        self.SWG_UI.graph_wave.showGrid(True, True)

    def handle_begin(self):
        """
        按下开始按钮  开新线程生成初始曲线
        """
        "==================开始，按钮设为不可用================"
        self.pushbutton_disabled()
        """===============清除之前的图像============"""
        self.ms.try_clear.emit(self.SWG_UI.graph_fyp)
        self.ms.try_clear.emit(self.SWG_UI.graph_wave)
        "=================计算设计谱================"
        self.gdc.sjp = np.zeros(self.nTyz)  # 设计谱点数为nTyz
        for i in range(self.nTyz):  # 得到一定场地条件下的设计谱
            self.gdc.sjp[i] = self.g * designRs(self.gdc.Tyz[i], self.gdc.Damp, self.ld, self.cd, self.fz, self.rare)
        "=================更新时间序列，可能输入了新的时长================"
        self.gdc.t = np.arange(0, self.Td + 0.01, 0.01)  # 人造地震动时间序列
        self.at = np.zeros_like(self.gdc.t)  # 初始化地震波
        self.nt = len(self.gdc.t)
        "=================生成随机相位和外包络线================"
        for i in range(self.npl):
            self.gdc.fai[i] = random.random() * 2 * np.pi  # 随机相位
        "================求外包络及强震持时=============="
        self.gdc.bl = np.zeros(self.nt)  # 外包络长度=时间长度
        for j in range(self.nt):
            self.gdc.bl[j] = ft(self.gdc.t[j], self.cd)
        "=================求各频率的幅值系数========="
        for i in range(self.npl):
            # 由设计谱计算功率谱
            # 近似转换公式 得到单边功率谱
            # self.SW[i] = - 2 * Damp / (np.pi * w[i]) * sjp[self.npl - 1 - i] ** 2 / np.log(
            #     -np.pi / (w[i] * strong_time) * np.log(1 - 0.15))
            this_aw = self.g * designRs(2 * np.pi / self.gdc.w[i], self.gdc.Damp, self.ld, self.cd, self.fz,
                                        self.rare)  # 找对应频率序列的反应谱值
            self.SW[i] = fyp2glp(this_aw, self.gdc.Damp, self.gdc.w[i], self.cd)
        # 由功率谱计算幅值谱
        self.gdc.Aw = np.zeros(self.npl)  # 频率幅值系数
        for i in range(self.npl):
            # 由功率谱计算幅值谱
            self.gdc.Aw[i] = np.sqrt(4 * self.SW[i] * self.delta_f * 2 * np.pi)  # 各频率对应的幅值系数
        "============在新线程中计算初始地震波============="
        self.thread_first_wave = Thread(target=self.first_wave, daemon=True)  # 设置为daemon线程
        self.thread_first_wave.start()

    def handle_easy_iter(self):
        """
        按下简单迭代按钮，开始简单迭代
        """
        if self.progress_machine == 0:  # 判断先进行了曲线生成 状态机为0 说明还没生成初始曲线
            QMessageBox.critical(self.UI_widget, '错误', '请先生成该参数下的初始曲线！', QMessageBox.Yes)
            return
        if self.progress_machine == 3:  # 状态机为3，说明已经使用高级算法进行优化过了，不再推荐使用简单迭代，强制返回
            QMessageBox.warning(self.UI_widget, '警告', '优化后再进行简单迭代可能损失精度！', QMessageBox.Yes)
            return

        "==================开始，按钮设为不可用================"
        self.pushbutton_disabled()
        # 实例化一个进度条
        self.pgb_easy = MyProgressBar()
        # 在子线程中计算简单迭代
        self.thread_easy = Thread(target=self.easy_iter, daemon=True)  # 设置为daemon线程
        self.thread_easy.start()
        self.SWG_UI.pushButton_exit.setEnabled(False)  # 不让退出

    def handle_advanced_algorithm(self):
        """
        按下高级优化按钮  选择算法  开新线程进行优化
        """
        if self.progress_machine == 0:  # 判断先进行了曲线生成 状态机为0 说明还没生成初始曲线
            QMessageBox.critical(self.UI_widget, '错误', '请先生成该参数下的初始曲线！', QMessageBox.Yes)
            return
        if self.progress_machine == 1:  # 生成初始曲线，还未完成简单迭代
            QMessageBox.warning(self.UI_widget, '警告', '请先使用简单迭代提高精度！', QMessageBox.Yes)
            return
        "==================开始，按钮设为不可用================"
        self.pushbutton_disabled()
        "===========进度条=============="
        self.pgb_advancing = MyProgressBar()
        # 建立新线程
        algorithm_dict = {'遗传算法': self.do_ga,
                          '粒子群算法': self.do_pso}
        algorithm_function = algorithm_dict[self.advancing_algorithm]
        self.thread_advancing = Thread(target=algorithm_function, daemon=True)  # 设置为daemon线程
        self.thread_advancing.start()

    def handle_input_wave(self):
        """
        响应导出按钮
        :return:
        """
        fileName, filetype = QFileDialog.getOpenFileName(self.SWG_UI, "选取文件",
                                                         "C:/",
                                                         "Text Files (*.txt)")  # 设置文件扩展名过滤,注意用双分号间隔
        if filetype == 'Text Files (*.txt)':
            print(fileName, filetype)
            try:
                a = np.loadtxt(fileName)
                print(a.shape)
            except ValueError:
                QMessageBox.warning(self.UI_widget, '错误', '文件格式不符！\n请重新核对！', QMessageBox.Yes | QMessageBox.No,
                                    QMessageBox.Yes)

        elif not filetype:
            QMessageBox.information(self.UI_widget, '提示', '数据未导出！', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)

    def first_wave(self):
        """
        新线程中 进行初始地震波处理
        """
        self.at = hecheng(self.gdc.t, self.gdc.w, self.gdc.fai, self.gdc.Aw, self.gdc.bl)  # 合成初始地震波
        self.MAcc = resspec(self.gdc.t, self.at, self.gdc.Tyz, self.gdc.Damp)  # 生成初始地震波曲线的反应谱
        self.error = (self.MAcc - self.gdc.sjp) / max(self.gdc.sjp)  # 计算曲线在各周期点误差
        # self.wave_SW = Aw ** 2 / (8 * np.pi * self.delta_f)  # 计算实际功率谱  直接反用功率谱————》》幅值谱的公式
        "================更新图像==============="
        self.update_graph()
        print('初始地震生成完成，反应谱最大误差为：{0}%'.format(max(abs(self.error)) * 100))
        "================计算小于设计谱10%的点的个数==============="
        print('当前反应谱中小于设计谱10%的点共有：{0}个'.format(calc_below(self.error)))
        "================结束，按钮可用==============="
        self.pushbutton_enabled()
        "================更改指示箭头============"
        self.set_arrow(2)

        self.progress_machine = 1  # 改变状态机

    def easy_iter(self):
        """
        简单迭代过程处理函数  新线程中运行
        """

        epoch = 1  # 迭代次数计数器
        error_record = max(abs(self.error))  # 记录误差
        this_Aw = self.gdc.Aw.copy()  # 复制一份 幅值系数
        Aw_record = self.gdc.Aw.copy()
        this_MAcc = self.MAcc.copy()
        "=====================开始迭代====================="
        while max(abs(self.error)) > self.rc:  # 入口条件
            scale1 = self.gdc.sjp / this_MAcc  # 首先计算当前的反应谱和设计谱之间的比例关系
            scale2 = np.ones(self.npl)  # 初始化比例修正系数
            for fp in range(self.npl):  # 对每个频率点都进行修正
                # 反应谱频率范围为0.2-33Hz
                # if 1 / f[fp] <= Tyz[0]:
                #     # 去头，对于超出反应谱最大频率33Hz的高频分量，缩放比例全都与33Hz相同
                #     scale2[fp] = scale1[0]
                if 1 / self.gdc.f[fp] >= self.gdc.Tyz[-1]:
                    # 去尾，对于低于反应谱最小频率0.2Hz的低频分量，缩放比例全都与0.2Hz相同
                    scale2[fp] = scale1[-1]
                else:
                    # 剩下的频率都包含在反应谱的频率内
                    # 如果一个频率换算得到的周期不在设计谱的坐标点上，则使用线性插值的方式计算出其幅值调整系数
                    # 先找出该频率点落在哪一段内
                    this_where = list(np.where(1 / self.gdc.f[fp] < self.gdc.Tyz)[0])  # 找该频率在反应谱上的频率区间（上界）
                    tp = this_where[0] - 1  # -1得到下界  【0】从list中取出数
                    scale2[fp] = (scale1[tp + 1] - scale1[tp]) / (self.gdc.Tyz[tp + 1] - self.gdc.Tyz[tp]) * (
                            1 / self.gdc.f[fp] - self.gdc.Tyz[tp]) + scale1[tp]
            this_Aw = this_Aw * scale2
            # 控制幅值谱使其对应功率谱在设计谱对应功率谱的30%-300%之间
            # 选用的功率谱模型算出来的反应谱都偏大  修正系数一般都小于1
            upper = np.sqrt(3)
            # lower = np.sqrt(0.3)
            this_Aw[np.where(this_Aw > upper * self.gdc.Aw)] = upper * self.gdc.Aw[
                np.where(this_Aw > upper * self.gdc.Aw)]
            # 控制放大倍率上限
            # this_Aw[np.where(this_Aw < lower * Aw)] = lower * Aw[np.where(this_Aw < lower * Aw)]
            # 更新地震波
            this_at = hecheng(self.gdc.t, self.gdc.w, self.gdc.fai, this_Aw, self.gdc.bl)  # 生成修正后的曲线
            # 更新反应谱
            this_MAcc = resspec(self.gdc.t, this_at, self.gdc.Tyz, self.gdc.Damp)  # 地震波曲线的反应谱
            # 更新误差曲线
            this_error = (this_MAcc - self.gdc.sjp) / max(self.gdc.sjp)  # 计算曲线在各周期点误差
            "===================过程记录=================="
            this_error_max = max(abs(this_error))
            if this_error_max < error_record:
                error_record = this_error_max
                Aw_record = this_Aw  # 记录下修正每次的幅值系数
            print('第{0}次简单迭代，反应谱最大误差为：{1}%'.format(epoch, max(abs(this_error)) * 100))
            # 更新进度条
            self.ms.try_update_progress_bar.emit(self.pgb_easy, epoch / self.max_iter * 100)
            epoch += 1
            if epoch > self.max_iter:  # 判断是否达到次数要求
                break
        # 选出简单迭代里面最好的 也能加速计算
        self.gdc.Aw = Aw_record
        # Aw = this_Aw
        # plt.plot(range(len(error_record)),error_record)
        # plt.show()
        # with open("error_record.txt",'w') as file:
        #     np.savetxt(file,error_record)
        self.at = hecheng(self.gdc.t, self.gdc.w, self.gdc.fai, self.gdc.Aw, self.gdc.bl)
        self.MAcc = resspec(self.gdc.t, self.at, self.gdc.Tyz, self.gdc.Damp)  # 生成初始地震波曲线的反应谱
        self.error = (self.MAcc - self.gdc.sjp) / max(self.gdc.sjp)  # 计算曲线在各周期点误差

        # self.wave_SW = Aw ** 2 / (8 * np.pi * self.delta_f)  # 计算实际功率谱  可以直接套公式
        print('简单迭代过程结束！\n当前反应谱最大相对误差：{0}%\n'
              '误差最大的点是{1}s处'.format(max(abs(self.error)) * 100, np.round(self.gdc.Tyz[np.argmax(abs(self.error))]), 3))
        print('当前反应谱中小于设计谱10%的点共有：{0}个'.format(calc_below(self.error)))
        self.update_graph()  # 更新图像
        # 进度条拉到100%  防止中途找到符合目标的值，停在中间
        self.ms.try_update_progress_bar.emit(self.pgb_easy, 100)
        # 发出满进度信号
        self.ms.full_progress.emit(self.pgb_easy, '优化完成！')
        "================结束，按钮可用==============="
        self.pushbutton_enabled()
        "================简单迭代结束，状态机改变============"
        self.progress_machine = 2
        "================更改指示箭头============"
        self.set_arrow(3)
        # 关闭进度条，显示结果
        self.ms.close_progress_bar.emit(self.pgb_easy)  # 关闭进度条
        if max(abs(self.error)) > 0.3:
            # 子线程里不能直接进行关闭进度条、抛出提示框的操作，必须通过动作信号
            self.ms.create_message_box.emit(r"warning(None,'警告','误差较大，"
                                            "为您重新生成曲线！请再次进行简单迭代！', QMessageBox.Yes)")
            self.handle_begin()  # 直接进行一次重新生成
        else:
            self.ms.create_message_box.emit(r"information(None,'结果','简单迭代过程结束！\n当前曲线最大反应谱误差为：{0}%\n"
                                            "当前曲线低于设计谱10%的点数为：{1}', QMessageBox.Yes)"
                                            .format(np.round(max(abs(self.error)) * 100, 3), calc_below(self.error)))

        self.SWG_UI.pushButton_exit.setEnabled(True)  # 恢复退出允许

    def do_ga(self):
        """
        执行遗传算法
        """
        print('\n\n==========Ga optimization begin now！==========')
        original_N_adjust = self.gdc.N_adjust
        for sag in range(self.gdc.advancing_iter):  # 多次使用ga  advancing_iter
            self.gdc.index_w = find_bad_w(self.gdc.w, abs(self.error), self.gdc.Tyz, original_N_adjust)  # 找出误差最大的频率点
            self.gdc.index_w = list(
                set(list(self.gdc.index_w) + (find_some_max(self.gdc.Aw, original_N_adjust, self.gdc.index_w))))
            self.gdc.N_adjust = len(self.gdc.index_w)
            print('\n\n第%d次迭代' % (sag + 1))
            print('要修正的频率点是：\n%s Hz' % self.gdc.f[self.gdc.index_w])
            """===============================实例化问题对象==========================="""
            problem = GaProblem(self.gdc)  # 生成问题对象
            """=================================种群设置==============================="""
            Encoding = 'RI'  # 编码方式
            NIND = self.gdc.N_adjust * 5  # 种群规模
            Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders)  # 创建区域描述器
            population = ea.Population(Encoding, Field, NIND)  # 实例化种群对象（此时种群还没被初始化，仅仅是完成种群对象的实例化）
            """===============================算法参数设置============================="""
            myAlgorithm = ea.soea_SEGA_templet(problem, population)  # 实例化一个算法模板对象
            # 单目标优化 精英保留策略
            myAlgorithm.MAXGEN = 20  # 最大进化代数
            myAlgorithm.mutOper.Pm = 0.5  # 变异概率
            myAlgorithm.recOper.XOVR = 0.7  # 重组概率
            myAlgorithm.drawing = 0  # 设置绘图方式（0：不绘图；1：绘制结果图；2：绘制目标空间过程动画；3：绘制决策空间过程动画）
            """===========================根据先验知识创建先知种群======================="""
            # # 认为保持原状是最不差的一个选择
            # prophet_Chrom = np.array([np.ones(N_adjust)])  # 1为先验个体
            # prophetPop = ea.Population(Encoding, Field, 1, prophet_Chrom)  # 实例化种群对象（设置个体数为1）
            # # 不需要解码的“实值编码”种群的染色体的每一位就对应着决策变量的实际值，即这种编码方式下 Phen 等价 Chrom。
            # prophetPop.Phen = prophetPop.Chrom
            # prophetPop.ObjV = [[max(abs(self.error))]]  # 计算先验种群目标函数值
            """===========================调用算法模板进行种群进化======================="""
            [_, obj_trace, var_trace] = myAlgorithm.run()  # 执行算法模板
            best_gen = np.argmin(problem.maxormins * obj_trace[:, 1])  # 记录最优种群个体是在哪一代
            best_ObjV = obj_trace[best_gen, 1]
            best_x = var_trace[best_gen, :]  # 最优个体

            "============判断当前寻找到的最优个体是否优于上次的结果，如果符合要求，更新参数；否则，放弃============  "
            this_Aw = self.gdc.Aw.copy()
            this_Aw[self.gdc.index_w] *= best_x
            this_at = hecheng(self.gdc.t, self.gdc.w, self.gdc.fai, this_Aw, self.gdc.bl)
            this_MAcc = resspec(self.gdc.t, this_at, self.gdc.Tyz, self.gdc.Damp)
            this_error = (this_MAcc - self.gdc.sjp) / max(self.gdc.sjp)
            if (max(abs(this_error)) < max(abs(self.error))) or \
                    (calc_below(this_error) < calc_below(self.error) and max(abs(this_error)) < 1.1 * max(
                        abs(self.error))):  # 接受条件
                self.gdc.Aw = this_Aw
                self.at = this_at
                self.MAcc = this_MAcc
                self.error = this_error
                # self.wave_SW = this_Aw ** 2 / (8 * np.pi * self.delta_f)  # 计算实际功率谱
                # 更新图像
                self.update_graph()
                print("此次搜索成功！！")
            else:
                print('此次搜索失败！！')
            # 更新进度条
            self.ms.try_update_progress_bar.emit(self.pgb_advancing, (1 + sag) / self.gdc.advancing_iter * 100)
            # 输出当前进化结果
            print('最优的目标函数值为：%s' % best_ObjV)
            print('最优的控制变量值为：')
            print(best_x)
            print('有效进化代数：%s' % (obj_trace.shape[0]))
            print('最优的一代是第 %s 代' % (best_gen + 1))
            print('评价次数：%s' % myAlgorithm.evalsNum)
            print('时间已过 %s 秒' % myAlgorithm.passTime)
            print('当前最大误差：%s' % max(abs(self.error) * 100))
            print('当前反应谱中小于设计谱10%的点共有：{0}个'.format(calc_below(self.error)))
            if calc_below(self.error) <= 5 and max(abs(self.error)) < 0.05:
                break

        print('==========ga优化结束！==========')
        self.gdc.N_adjust = original_N_adjust
        "=================优化完成，在进度条上提示，并等待删除进度条==================="
        # 进度条拉到100%  防止中途找到符合目标的值，停在中间
        self.ms.try_update_progress_bar.emit(self.pgb_advancing, 100)
        self.ms.full_progress.emit(self.pgb_advancing, '优化完成，点此退出！')
        "================结束，按钮可用==============="
        self.pushbutton_enabled()
        "================更改指示箭头============"
        self.set_arrow(4)

        "================迭代结束，状态机更新============="
        self.progress_machine = 3
        self.ms.close_progress_bar.emit(self.pgb_advancing)  # 关闭进度条
        self.ms.create_message_box.emit(r"information( None,'结果','遗传算法优化过程结束！\n当前曲线最大反应谱误差为：{0}%\n"
                                        "当前曲线低于设计谱10%的点数为：{1}', QMessageBox.Yes)"
                                        .format(np.round(max(abs(self.error)) * 100, 3), calc_below(self.error)))

    def do_pso(self):
        """
        执行粒子群算法
        :return:
        """
        print('\n\n==========PSO optimization begin now！==========')
        original_N_adjust = self.gdc.N_adjust
        for sag in range(self.gdc.advancing_iter):  # 多次使用pso
            t1 = time.time()
            self.gdc.index_w = find_bad_w(self.gdc.w, abs(self.error), self.gdc.Tyz, original_N_adjust)  # 找出误差最大的频率点
            self.gdc.index_w = list(
                set(list(self.gdc.index_w) + (find_some_max(self.gdc.Aw, original_N_adjust, self.gdc.index_w))))
            self.gdc.N_adjust = len(self.gdc.index_w)
            print('\n\n第%d次迭代' % (sag + 1))
            print('要修正的频率点是：\n%s Hz' % self.gdc.f[self.gdc.index_w])
            ilb = [0.5] * self.gdc.N_adjust
            iub = [2] * self.gdc.N_adjust
            pso = PSO(func=opt_aim_Func, dim=self.gdc.N_adjust, gdc=self.gdc, pop=self.gdc.N_adjust * 5,
                      max_iter=self.gdc.advancing_iter, lb=ilb,
                      ub=iub)
            "======运行算法======="
            pso.run()
            "============用最优个体更新参数============  "
            this_Aw = self.gdc.Aw.copy()
            this_Aw[self.gdc.index_w] *= pso.gbest_x
            this_at = hecheng(self.gdc.t, self.gdc.w, self.gdc.fai, this_Aw, self.gdc.bl)
            this_MAcc = resspec(self.gdc.t, this_at, self.gdc.Tyz, self.gdc.Damp)
            this_error = (this_MAcc - self.gdc.sjp) / max(self.gdc.sjp)
            if (max(abs(this_error)) < max(abs(self.error))) or \
                    (calc_below(this_error) < calc_below(self.error) and max(abs(this_error)) < 1.1 * max(
                        abs(self.error))):  # 接受条件
                self.gdc.Aw = this_Aw
                self.at = this_at
                self.MAcc = this_MAcc
                self.error = this_error
                # self.wave_SW = this_Aw ** 2 / (8 * np.pi * self.delta_f)  # 计算实际功率谱
                self.update_graph()
                print("此次搜索成功！！")
            else:
                print('此次搜索失败！！')
            "==============更新进度条==============="
            self.ms.try_update_progress_bar.emit(self.pgb_advancing, (1 + sag) / self.gdc.advancing_iter * 100)
            # 输出当前进化结果
            " ============结果============="
            t2 = time.time()
            print('最优的目标函数值为：%s' % pso.gbest_y)
            print('最优的控制变量值为：')
            print(pso.gbest_x)
            print('当前最大误差：%s' % max(abs(self.error) * 100))
            print('当前反应谱中小于设计谱10%的点共有：{0}个'.format(calc_below(self.error)))
            print('用时：%s s' % (t2 - t1))

            if calc_below(self.error) <= 5 and max(abs(self.error)) < 0.05:
                break

        print('==========pso优化结束！==========')
        self.gdc.N_adjust = original_N_adjust
        "=================优化完成，在进度条上提示，并等待删除进度条==================="
        # 进度条拉到100%  防止中途找到符合目标的值，停在中间
        self.ms.try_update_progress_bar.emit(self.pgb_advancing, 100)
        self.ms.full_progress.emit(self.pgb_advancing, '优化完成，点此退出！')
        "================结束，按钮可用==============="
        self.pushbutton_enabled()
        "================更改指示箭头============"
        self.set_arrow(4)
        "================迭代结束，状态机更新============="
        self.progress_machine = 3
        self.ms.close_progress_bar.emit(self.pgb_advancing)  # 关闭进度条
        self.ms.create_message_box.emit(r"information( None,'结果','粒子群算法优化过程结束！\n当前曲线最大反应谱误差为：{0}%\n"
                                        "当前曲线低于设计谱10%的点数为：{1}', QMessageBox.Yes)"
                                        .format(np.round(max(abs(self.error)) * 100, 3), calc_below(self.error)))

    def update_graph(self):
        """
        更新所有图像
        :return:
        """
        # 更新反应谱图像
        self.ms.try_clear.emit(self.SWG_UI.graph_fyp)  # 发出信号清除原图像
        self.ms.try_plot.emit(self.SWG_UI.graph_fyp, list(self.gdc.Tyz), list(self.gdc.sjp), 'r', '设计要求反应谱')  # 先画一遍设计谱
        self.ms.try_plot.emit(self.SWG_UI.graph_fyp, list(self.gdc.Tyz), list(self.MAcc), 'k', '人工地震波反应谱')

        # 更新地震波图像
        self.ms.try_clear.emit(self.SWG_UI.graph_wave)  # 发出信号清除原图像
        self.ms.try_plot.emit(self.SWG_UI.graph_wave, list(self.gdc.t), list(self.at), 'k', '地震波时程曲线')  # 发出画图信号

    def select_advancing_algorithm(self, i):
        """
       获取当前选择的优化算法类型  返回string
       """
        self.advancing_algorithm = i
        print('优化算法为：%s' % i)

    def output(self):
        """
        数据导出函数
        """
        if self.progress_machine == 0:
            QMessageBox.critical(self.UI_widget, '错误', '请先生成数据！', QMessageBox.Yes)
            return
        (x, y) = (self.gdc.t, self.at)
        # 路径选择
        filePath, file_type = QFileDialog.getSaveFileName(
            self.UI_widget,  # 父窗口对象
            "选择要保存的文件",  # 标题
            r"d:\\data",  # 起始目录
            "Text Files(*.txt);;Excel Files(*.xls);;Excel Files(*.xlsx)")  # 选择类型过滤项，过滤内容在括号中
        # 合成一个ndarray
        if file_type:
            # if file_type == 'Text Files(*.txt)':
            with open(filePath, 'w') as file:
                z = np.concatenate((x.reshape(len(x), 1), y.reshape(len(y), 1)), axis=1)
                np.savetxt(file, z, fmt="%.2f\t          %.6f\t",
                           header="人工地震动时程曲线\n"
                                  "场地类型：      {0}\n"
                                  "地震烈度：       {1}\n"
                                  "设计地震分组： {2}\n"
                                  "地震罕见度：   {3}\n"
                                  "阻尼比：          {4}\n"
                                  "时间(s)        加速度(m/s^2)"
                           .format(self.SWG_UI.comboBox_cd.currentText(),
                                   self.SWG_UI.comboBox_ld.currentText(),
                                   self.SWG_UI.comboBox_fz.currentText(),
                                   self.SWG_UI.comboBox_rare.currentText(),
                                   self.gdc.Damp))
            file.close()
            output_data_dir = {1: "初始地震波曲线", 2: "简单迭代后的地震波曲线", 3: "算法优化后的地震波曲线"}
            QMessageBox.information(self.UI_widget, '成功', '数据成功导出！\n您导出的是：{0}'
                                    .format(output_data_dir[self.progress_machine]),
                                    QMessageBox.Yes)
        # 未选择输出
        else:
            QMessageBox.information(self.UI_widget, '提示', '数据未导出！', QMessageBox.Yes)

    def quit_program(self):
        """
        程序退出
        """
        try:
            if self.thread_advancing.is_alive() or self.thread_easy.is_alive():
                reply = QMessageBox.warning(self.UI_widget, '警告', '优化过程正在进行，是否确定退出？', QMessageBox.Yes | QMessageBox.No,
                                            QMessageBox.Yes)
                if reply == QtWidgets.QMessageBox.Yes:
                    if self.thread_first_wave.is_alive():
                        stop_thread(self.thread_first_wave)
                    if self.thread_advancing.is_alive():
                        print("发现算法优化线程运行中，正在关闭！")
                        stop_thread(self.thread_advancing)
                        # 关闭进度条
                        self.ms.close_progress_bar.emit(self.pgb_advancing)
                    if self.thread_easy.is_alive():
                        print("发现简单迭代线程运行中，正在关闭！")
                        stop_thread(self.thread_easy)
                        # 关闭进度条
                        self.ms.close_progress_bar.emit(self.pgb_easy)
                    print("已退出地震波时程曲线生成模块！")
                    self.UI_widget.close()
                    self.father.show()
            else:  # 子线程都已经结束
                print("已退出地震波时程曲线生成模块！")
                self.UI_widget.close()
                self.father.show()

        except AttributeError:  # 子线程都不存在
            print("已退出地震波时程曲线生成模块！")
            self.UI_widget.close()
            self.father.show()

    def set_cd(self, i):  # 没法获得index值，信号返回的是当前被选择的内容，导致分类比较繁琐
        """
        设置场地类型
        :param i:
        :return:
        """
        if i == 'Ⅰ(0)':
            self.cd = 0
        elif i == 'Ⅰ(1)':
            self.cd = 1
        elif i == 'Ⅱ':
            self.cd = 2
        elif i == 'Ⅲ':
            self.cd = 3
        elif i == 'Ⅳ':
            self.cd = 4
        print('场地类型为：%s' % i)
        self.progress_machine = 0  # 状态机置0
        self.set_arrow(1)  # 箭头指向开始

    def set_ld(self, i):
        """
        设置烈度
        :param i:
        :return:
        """
        if i == '6(0.05g)':
            self.ld = 0
        elif i == '7(0.10g)':
            self.ld = 1
        elif i == '7(0.15g)':
            self.ld = 2
        elif i == '8(0.20g)':
            self.ld = 3
        elif i == '8(0.25g)':
            self.ld = 4
        elif i == '8(0.30g)':
            self.ld = 5
        elif i == '9(0.40g)':
            self.ld = 6
        elif i == '10(0.50g)':
            self.ld = 7
        print('烈度：%s' % i)
        self.progress_machine = 0  # 状态机置0
        self.set_arrow(1)  # 箭头指向开始

    def set_fz(self, i):
        """
        设置分组
        :param i:
        :return:
        """
        self.fz = int(i)
        print('分组：%s' % i)
        self.progress_machine = 0  # 状态机置0
        self.set_arrow(1)  # 箭头指向开始

    def set_rare(self, i):
        """
        设置罕见度
        """
        if i == '基本地震动':
            self.rare = 'basic'
            print('基本地震动')

        elif i == '罕遇地震动':
            self.rare = 'rare'
            print('罕遇地震动')

        elif i == '多遇地震动':
            self.rare = 'frequent'
            print('多遇地震动')

        elif i == '极罕遇地震动':
            self.rare = 'very rare'
            print('极罕遇地震动')
        self.progress_machine = 0  # 状态机置0
        self.set_arrow(1)  # 箭头指向开始

    def set_Td(self):  # 持时
        """
        设置地震持时
        :return:
        """
        try:
            input_Td = float(self.SWG_UI.lineEdit_Td.text())
            if input_Td == self.Td:
                return
            self.Td = input_Td
            "==============判断输入是否正常=============="
            if self.Td < 15 or self.Td > 30:  # 地震持时检查
                self.Td = 20
                self.SWG_UI.lineEdit_Td.setText('20')
                self.SWG_UI.lineEdit_Td.setPlaceholderText('20')
                QMessageBox.warning(self.UI_widget, '错误', '地震持时推荐在15~30s范围内！\n请重新输入！', QMessageBox.Yes)
            else:
                print("地震持时：%s" % self.Td)
        except ValueError:  # 过滤符号，保证传入的是数字 但不能马上判断输入的数字是否符合要求，因为该信号每次输入都触发，可能未输入完成
            print('参数错误')
            print('地震时长还原为默认值20s')
            self.Td = 20
            self.SWG_UI.lineEdit_Td.setText('20')
            self.SWG_UI.lineEdit_Td.setPlaceholderText('20')
        self.progress_machine = 0  # 状态机置0
        self.set_arrow(1)  # 箭头指向开始

    def set_Damp(self):
        """
        设置阻尼比
        :return:
        """
        try:
            input_Damp = float(self.SWG_UI.lineEdit_Damp.text())
            if input_Damp == self.gdc.Damp:
                return
            self.gdc.Damp = input_Damp
            if self.gdc.Damp <= 0 or self.gdc.Damp >= 1:  # 超界，设置默认值
                self.gdc.Damp = 0.05
                self.SWG_UI.lineEdit_Damp.setText('0.05')
                self.SWG_UI.lineEdit_Damp.setPlaceholderText('0.05')
                QMessageBox.warning(self.UI_widget, '错误', '阻尼比应取0~1之间的数！\n请重新输入！', QMessageBox.Yes)
            else:  # 正常
                print('阻尼比：%s' % self.gdc.Damp)

        except ValueError:
            print('参数错误')
            print('阻尼比设置为默认值0.05')
            self.gdc.Damp = 0.05
            self.SWG_UI.lineEdit_Damp.setText('0.05')
            self.SWG_UI.lineEdit_Damp.setPlaceholderText('0.05')
        self.progress_machine = 0  # 状态机置0
        self.set_arrow(1)  # 箭头指向开始

    def set_rc(self):
        """
        设置曲线容差
        :return:
        """
        try:
            self.rc = float(self.SWG_UI.lineEdit_rc.text())
            if self.rc <= 0 or self.rc > 0.4:  # 阻尼比检查
                self.rc = 0.2
                self.SWG_UI.lineEdit_rc.setText('0.2')
                self.SWG_UI.lineEdit_rc.setPlaceholderText('0.2')
                QMessageBox.warning(self.UI_widget, '警告', '简单迭代的目标误差建议设定在0~0.4之间！\n请重新输入！', QMessageBox.Yes)
            else:  # 正常
                print('简单迭代目标误差：%s' % self.rc)
        except ValueError:
            print('参数错误')
            print('简单迭代误差限设置为默认值 0.2')
            self.rc = 0.2
            self.SWG_UI.lineEdit_rc.setText('')
            self.SWG_UI.lineEdit_rc.setPlaceholderText('0.2')

    def set_max_iter(self):
        """
        设置最大迭代次数
        :return:
        """
        try:
            self.max_iter = int(self.SWG_UI.lineEdit_max_iter.text())
            if self.max_iter < 10 or self.max_iter > 100:
                self.max_iter = 20
                self.SWG_UI.lineEdit_max_iter.setText('20')
                self.SWG_UI.lineEdit_max_iter.setPlaceholderText('20')
                QMessageBox.warning(self.UI_widget, '警告', '简单迭代的次数建议在10~100次之间！\n请重新输入！', QMessageBox.Yes)
                return
            print('简单迭代最大次数：%s' % self.max_iter)
        except ValueError:
            print('参数错误')
            print('简单迭代最大次数设置为默认值10')
            self.max_iter = 20
            self.SWG_UI.lineEdit_max_iter.setText('')
            self.SWG_UI.lineEdit_max_iter.setPlaceholderText('20')

    def set_advancing_iter(self):
        """
        设置遗传迭代次数
        :return:
        """
        try:
            self.gdc.advancing_iter = int(self.SWG_UI.lineEdit_advancing_iter.text())
            if self.gdc.advancing_iter <= 0 or self.gdc.advancing_iter > 20:  # 阻尼比检查
                self.gdc.advancing_iter = 10
                self.SWG_UI.lineEdit_advancing_iter.setText('10')
                self.SWG_UI.lineEdit_advancing_iter.setPlaceholderText('10')
                QMessageBox.warning(self.UI_widget, '警告', '建议优化迭代次数在5~20次之间！\n请重新输入！', QMessageBox.Yes)
                return
            print('优化算法最大迭代次数：%s' % self.gdc.advancing_iter)
        except ValueError:
            print('参数错误')
            print('优化迭代次数设置为默认值10')
            self.gdc.advancing_iter = 10
            self.SWG_UI.lineEdit_advancing_iter.setText('')
            self.SWG_UI.lineEdit_advancing_iter.setPlaceholderText('10')

    def set_N_adjust(self):
        """
        设置用于调整频率点数
        :return:
        """
        try:
            self.gdc.N_adjust = int(self.SWG_UI.lineEdit_N_adjust.text())
            if self.gdc.N_adjust <= 0 or self.gdc.N_adjust > 20:
                self.gdc.N_adjust = 5
                self.SWG_UI.lineEdit_N_adjust.setText('5')
                self.SWG_UI.lineEdit_N_adjust.setPlaceholderText('5')
                QMessageBox.warning(self.UI_widget, '警告', '建议频率调整点数在5~20个之间！\n请重新输入！', QMessageBox.Yes)
            else:
                print('选择优化的频率点数：%s' % self.gdc.N_adjust)
        except ValueError:
            print('参数错误')
            print('优化迭代调整点数设置为默认值5')
            self.gdc.N_adjust = 5
            self.SWG_UI.lineEdit_N_adjust.setText('')
            self.SWG_UI.lineEdit_N_adjust.setPlaceholderText('5')

    def pushbutton_enabled(self):
        """
        按钮恢复可用性
        :return:
        """
        self.SWG_UI.pushButton_begin.setEnabled(True)
        self.SWG_UI.pushButton_advancing_begin.setEnabled(True)
        self.SWG_UI.pushButton_easy_iter.setEnabled(True)
        self.SWG_UI.pushButton_output.setEnabled(True)

        self.SWG_UI.comboBox_cd.setEnabled(True)  # 场地类型按钮
        self.SWG_UI.comboBox_ld.setEnabled(True)  # 烈度按钮
        self.SWG_UI.comboBox_fz.setEnabled(True)  # 分组按钮
        self.SWG_UI.comboBox_rare.setEnabled(True)  # 罕见度按钮

        self.SWG_UI.lineEdit_Damp.setEnabled(True)  # 阻尼比设置框
        self.SWG_UI.lineEdit_Td.setEnabled(True)  # 地震持时设置框
        self.SWG_UI.lineEdit_rc.setEnabled(True)  # 最大误差设置框
        self.SWG_UI.lineEdit_max_iter.setEnabled(True)  # 最大迭代次数设置框
        self.SWG_UI.lineEdit_N_adjust.setEnabled(True)  # 遗传算法调整点数设置
        self.SWG_UI.lineEdit_advancing_iter.setEnabled(True)  # 遗传算法迭代次数设置

        # 算法选择按钮组
        self.SWG_UI.comboBox_algorithm.setEnabled(True)

    def pushbutton_disabled(self):  # 在优化时，让按钮不可用
        """
        按钮失去可用性
        :return:
        """
        self.SWG_UI.pushButton_begin.setEnabled(False)
        self.SWG_UI.pushButton_advancing_begin.setEnabled(False)
        self.SWG_UI.pushButton_easy_iter.setEnabled(False)
        self.SWG_UI.pushButton_output.setEnabled(False)

        self.SWG_UI.comboBox_cd.setEnabled(False)  # 场地类型按钮
        self.SWG_UI.comboBox_ld.setEnabled(False)  # 烈度按钮
        self.SWG_UI.comboBox_fz.setEnabled(False)  # 分组按钮
        self.SWG_UI.comboBox_rare.setEnabled(False)  # 罕见度按钮

        self.SWG_UI.lineEdit_Damp.setEnabled(False)  # 阻尼比设置框
        self.SWG_UI.lineEdit_Td.setEnabled(False)  # 地震持时设置框
        self.SWG_UI.lineEdit_rc.setEnabled(False)  # 最大误差设置框
        self.SWG_UI.lineEdit_max_iter.setEnabled(False)  # 最大迭代次数设置框
        self.SWG_UI.lineEdit_N_adjust.setEnabled(False)  # 遗传算法调整点数设置
        self.SWG_UI.lineEdit_advancing_iter.setEnabled(False)  # 遗传算法迭代次数设置

        # 算法选择按钮组
        self.SWG_UI.comboBox_algorithm.setEnabled(False)

    def set_arrow(self, i):
        """
        设置箭头显示及按钮颜色
        :param i:
        :return:
        """
        # 先把所有箭头设置为空
        self.SWG_UI.arrow1.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')
        self.SWG_UI.arrow2.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')
        self.SWG_UI.arrow3.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')
        self.SWG_UI.arrow4.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')

        self.SWG_UI.h_arrow1.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')
        self.SWG_UI.h_arrow2.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')
        self.SWG_UI.h_arrow3.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')
        self.SWG_UI.h_arrow4.setStyleSheet('QLabel{border-image:url(./source/picture/blank.jpg);}')
        arrow_list = [[self.SWG_UI.arrow1, self.SWG_UI.h_arrow1], [self.SWG_UI.arrow2, self.SWG_UI.h_arrow2],
                      [self.SWG_UI.arrow3, self.SWG_UI.h_arrow3], [self.SWG_UI.arrow4, self.SWG_UI.h_arrow4]]
        arrow = arrow_list[i - 1]
        arrow[0].setStyleSheet('QLabel{border-image:url(./source/picture/arrow_black.jpg);}')
        arrow[1].setStyleSheet('QLabel{border-image:url(./source/picture/h_arrow.jpg);}')

        color_button_list = [self.SWG_UI.pushButton_begin, self.SWG_UI.pushButton_easy_iter,
                             self.SWG_UI.pushButton_advancing_begin, self.SWG_UI.pushButton_output]
        color_button = color_button_list[i - 1]
        color_button.setStyleSheet("QPushButton{font: 20pt '楷体'}"
                                   "QPushButton{color:black}"
                                   "QPushButton:hover{color:red}"
                                   "QPushButton{background-color:rgb(0,255,255)}"
                                   "QPushButton{border:2px}"
                                   "QPushButton{border-radius:10px}"
                                   "QPushButton{padding:2px 4px}")
        for i in color_button_list:
            if i != color_button:
                i.setStyleSheet("QPushButton{font: 16pt '楷体'}"
                                "QPushButton{color:black}"
                                "QPushButton:hover{color:red}"
                                "QPushButton{background-color:rgb(249,210,255)}"
                                "QPushButton{border:2px}"
                                "QPushButton{border-radius:10px}"
                                "QPushButton{padding:2px 4px}")


class MySignals(QObject):
    """
    定义动作信号类  用于在子线程中发出 在主线程中执行 否则可能会导致主线程中的执行出错
    """
    # 画图信号 QFrame： 画布 list分别为x、y轴数据
    try_plot = pyqtSignal(QFrame, list, list, str, str)
    # 清除图像信号
    try_clear = pyqtSignal(QFrame)
    # 更新进度条信号
    try_update_progress_bar = pyqtSignal(QWidget, float)
    # 进度条满信号
    full_progress = pyqtSignal(QWidget, str)
    # 关闭进度条信号
    close_progress_bar = pyqtSignal(QWidget)
    # 生成信息框信号，用于子线程里抛出信息框
    create_message_box = pyqtSignal(str)

    def __init__(self, parent=None):
        """
        初始化
        :param parent:
        """
        super(MySignals, self).__init__(parent)
        self.try_clear.connect(self.clear)
        self.try_plot.connect(self.draw)
        self.try_update_progress_bar.connect(self.update_progress_bar)
        self.full_progress.connect(self.full_progress_close)
        self.close_progress_bar.connect(self.cpb)
        self.create_message_box.connect(self.cmb)

    @staticmethod
    def draw(graph, x, y, color, name):
        """
        画图
        :param graph:
        :param x:
        :param y:
        :param color:
        :param name:
        :return:
        """
        graph.addLegend(offset=(-10, 5))  # 图例位置，正数表示从左 上 偏置，负数表示从右  下 偏置
        graph.plot(x, y, pen=color, name=name)

    @staticmethod
    def clear(graph):
        """
        清理图形
        :param graph:
        :return:
        """
        graph.clear()

    @staticmethod
    def update_progress_bar(progress_bar_window, data):
        """
        更新进度条
        :param progress_bar_window:
        :param data:
        :return:
        """
        try:
            progress_bar_window.pbar.setValue(data)
        except AttributeError:
            print("进度条已销毁，忽略操作")

    @staticmethod
    def full_progress_close(progress_bar_window, string):
        """
        进度已满，按钮连接上关闭功能
        """
        try:
            progress_bar_window.tip.setText(string)
            progress_bar_window.tip.clicked.connect(progress_bar_window.close_bar)
        except AttributeError:
            print("进度条已销毁，忽略操作")

    @staticmethod
    def cpb(bar):
        """
        关闭进度条 close progress bar
        :param bar:
        :return:
        """
        try:
            bar.close()
        except AttributeError:
            print("进度条已销毁，忽略操作")

    @staticmethod
    def cmb(content):
        """
        create message bar
        :param content:
        :return:
        """
        create = r'QMessageBox.{0}'.format(content)
        exec(create)


class MyProgressBar(QWidget):
    """
    进度条类
    """

    def __init__(self):
        super().__init__()
        self.pbar = QProgressBar(self)
        self.tip = QPushButton(self)
        self.initpbUI()

    def initpbUI(self):
        # 设置窗口位置
        self.setGeometry(0, 0, 400, 200)
        self.setWindowTitle('进度条')
        screen = QDesktopWidget().screenGeometry()  # 获取屏幕尺寸
        size = self.geometry()  # 控件尺寸
        self.move((screen.width() - size.width()) / 2,
                  (screen.height() - size.height()) / 2)

        self.setStyleSheet("background-color:rgb(255,255,255)")
        # 进度条
        self.pbar.setGeometry(60, 50, 300, 40)
        self.pbar.setStyleSheet("background-color:rgb(255,255,255)")
        # 设置提示的位置和字体
        self.tip.setGeometry(80, 120, 240, 40)
        self.tip.setText('优化进行中，请稍候！')
        self.tip.setFont(QFont("Roman times", 14, QFont.Bold))
        self.tip.setStyleSheet("border:none;")
        # 给进度条初值
        self.pbar.setValue(0)
        # 设置窗口最前端
        self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint |  # 最小化按钮
                            QtCore.Qt.WindowCloseButtonHint)  # 使能关闭按钮
        # QtCore.Qt.WindowStaysOnTopHint)  # 窗体总在最前端
        self.setFixedSize(self.width(), self.height())  # 固定窗体大小
        self.show()

    def close_bar(self):
        self.close()


class GaProblem(ea.Problem):
    """
    遗传算法问题类   继承Problem父类
    单目标优化问题
    仅考虑最大相对误差
    """

    def __init__(self, gdc: GlobalDataContainer):
        self.gdc = gdc
        name = 'MyGaProblem'  # 初始化name（函数名称，可以随意设置）
        M = 1  # 初始化M（目标维数）
        maxormins = [1] * M  # 初始化maxormins（目标最小最大化标记列表，1：最小化该目标；-1：最大化该目标）
        Dim = self.gdc.N_adjust  # 初始化Dim（决策变量维数）
        varTypes = [0] * Dim  # 初始化varTypes（决策变量的类型，元素为0表示对应的变量是连续的；1表示是离散的）
        lb = [0.5] * Dim  # 决策变量下界
        ub = [2] * Dim  # 决策变量上界
        lbin = [1] * Dim  # 决策变量下边界（0表示不包含该变量的下边界，1表示包含）
        ubin = [1] * Dim  # 决策变量上边界（0表示不包含该变量的上边界，1表示包含）
        # 调用父类构造方法完成实例化
        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)

    def aimFunc(self, pop):  # 目标函数
        x = pop.Phen  # 得到决策变量矩阵   大小： N_adjust*5    X   N_adjust 即 种群个体数*变量长度
        # 传一个参数进来 表示进行的是调频/调幅
        # 将Aw拓展为 len(Aw)* 种群个体数 的二维数组
        Aw2 = np.dot(self.gdc.Aw.copy().reshape(len(self.gdc.Aw), 1), np.ones((1, 5 * self.gdc.N_adjust)))
        for i in range(self.gdc.N_adjust):  # 修正幅值系数
            Aw2[self.gdc.index_w[i], :] *= x[:, i]
        at_this = hecheng(self.gdc.t, self.gdc.w, self.gdc.fai, Aw2, self.gdc.bl)  # 合成二维曲线数组
        # 计算进化后的新个体对于曲线的改进效果
        MAcc_this = resspec(self.gdc.t, at_this, self.gdc.Tyz, self.gdc.Damp)  # 生成反应谱  大小： 反应谱点数*种群个体数
        this_sjp = np.dot(self.gdc.sjp.copy().reshape(len(self.gdc.sjp), 1),
                          np.ones((1, 5 * self.gdc.N_adjust)))  # 对设计谱进行变形 拓宽

        # error_this = (MAcc_this - this_sjp) / max(this_sjp)  # 计算各点误差

        error_this = (MAcc_this - this_sjp) / max(self.gdc.sjp)  # 计算各点误差

        # 赋值给pop种群对象的ObjV属性  矩阵 每一行记录每一个个体的目标函数值
        # 大小： 种群个体数*1
        f1 = np.max(abs(error_this), axis=0).reshape(5 * self.gdc.N_adjust, 1)
        pop.ObjV = f1
        below_num = np.zeros(5 * self.gdc.N_adjust)  # 计算反应谱小于设计谱10%的点数
        for i in range(5 * self.gdc.N_adjust):
            below_num[i] = calc_below(error_this[:, i])
        pop.CV = below_num.reshape(5 * self.gdc.N_adjust, 1) - 5  # 罚函数设置为误差超过10%的点数 -5
        # 单目标优化以减小最大误差为目标


class SkoBase(metaclass=ABCMeta):
    """
    粒子群算法基类
    """

    def register(self, operator_name, operator, *args, **kwargs):
        """
        regeister udf to the class
        :param operator_name: string
        :param operator: a function, operator itself
        :param args: arg of operator
        :param kwargs: kwargs of operator
        :return:
        """

        def operator_wapper(*wrapper_args):
            return operator(*(wrapper_args + args), **kwargs)

        setattr(self, operator_name, types.MethodType(operator_wapper, self))
        return self


class PSO(SkoBase):
    """
    PSO问题模板
    Parameters
    --------------------
    func : function
        The func you want to do optimal
    dim : int
        Number of dimension, which is number of parameters of func.
    pop : int
        Size of population, which is the number of Particles. We use 'pop' to keep accordance with GA
    max_iter : int
        Max of iter iterations

    Attributes
    ----------------------
    pbest_x : array_like, shape is (pop,dim)
        best location of every particle in history
    pbest_y : array_like, shape is (pop,1)
        best image of every particle in history
    gbest_x : array_like, shape is (1,dim)
        general best location for all particles in history
    gbest_y : float
        general best image  for all particles in history
    gbest_y_hist : list
        gbest_y of every iteration
    """

    def __init__(self, func, dim, gdc: GlobalDataContainer, pop=40, max_iter=150, lb=None, ub=None, w=0.8, c1=0.5,
                 c2=0.5):
        self.gdc = gdc
        self.func = func
        self.w = w  # inertia
        self.cp, self.cg = c1, c2  # parameters to control personal best, global best respectively
        self.pop = pop  # number of particles
        self.dim = dim  # dimension of particles, which is the number of variables of func
        self.max_iter = max_iter  # max iter

        self.has_constraints = not (lb is None and ub is None)
        self.lb = -np.ones(self.dim) if lb is None else np.array(lb)
        self.ub = np.ones(self.dim) if ub is None else np.array(ub)
        assert self.dim == len(self.lb) == len(self.ub), 'dim == len(lb) == len(ub) is not True'
        assert np.all(self.ub > self.lb), 'upper-bound must be greater than lower-bound'

        self.X = np.random.uniform(low=self.lb, high=self.ub, size=(self.pop, self.dim))
        v_high = self.ub - self.lb
        self.V = np.random.uniform(low=-v_high, high=v_high, size=(self.pop, self.dim))  # speed of particles
        self.Y = self.cal_y()  # y = f(x) for all particles
        self.pbest_x = self.X.copy()  # personal best location of every particle in history
        self.pbest_y = self.Y.copy()  # best image of every particle in history
        self.gbest_x = np.zeros((1, self.dim))  # global best location for all particles
        self.gbest_y = np.inf  # global best y for all particles
        self.gbest_y_hist = []  # gbest_y of every iteration
        self.update_gbest()

        # record verbose values
        self.record_mode = False
        self.record_value = {'X': [], 'V': [], 'Y': []}

    def update_V(self):
        r1 = np.random.rand(self.pop, self.dim)
        r2 = np.random.rand(self.pop, self.dim)
        self.V = self.w * self.V + \
                 self.cp * r1 * (self.pbest_x - self.X) + \
                 self.cg * r2 * (self.gbest_x - self.X)

    def update_X(self):
        self.X = self.X + self.V

        if self.has_constraints:
            self.X = np.clip(self.X, self.lb, self.ub)

    def cal_y(self):
        # calculate y for every x in X
        self.Y = self.func(self.X, self.gdc).reshape(-1, 1)
        return self.Y

    def update_pbest(self):
        """
        personal best
        :return:
        """
        self.pbest_x = np.where(self.pbest_y > self.Y, self.X, self.pbest_x)
        self.pbest_y = np.where(self.pbest_y > self.Y, self.Y, self.pbest_y)

    def update_gbest(self):
        """
        global best
        :return:
        """
        if self.gbest_y > self.Y.min():
            self.gbest_x = self.X[self.Y.argmin(), :].copy()
            self.gbest_y = self.Y.min()

    def recorder(self):
        if not self.record_mode:
            return
        self.record_value['X'].append(self.X)
        self.record_value['V'].append(self.V)
        self.record_value['Y'].append(self.Y)

    def run(self, max_iter=None):
        self.max_iter = max_iter or self.max_iter
        for iter_num in range(self.max_iter):
            self.update_V()
            self.recorder()
            self.update_X()
            self.cal_y()
            self.update_pbest()
            self.update_gbest()

            self.gbest_y_hist.append(self.gbest_y)
        return self

    fit = run


def find_local_min(x):
    """找到低频段 局部最小值"""
    for i in range(len(x)):
        if x[i - 1] < x[i] < x[i + 1]:  # 有上升趋势的第一段
            return i - 1
        else:
            pass


def find_some_max(x, n, outer):
    """
    用于找到数列中前几个最大值的索引
    :param x: 数列
    :param n: 最值数量
    :param outer: 排除的索引
    :return:
    """
    temp = []
    xx = list(x.copy())
    while len(temp) < n:
        candidate = xx.index(max(xx))
        if candidate not in outer:
            temp.append(candidate)
        xx[candidate] = 0
    return temp


def calc_below(x, n=-0.1):
    """
    计算一个数列中小于n的个数
    :param x: 数列
    :param n: 判断限
    :return:
    """
    xx = x.copy()
    xx[np.where(x < n)] = 1
    xx[np.where(x >= n)] = 0
    return int(sum(xx))


def opt_aim_Func(x, param: GlobalDataContainer):
    """
    优化目标函数
    pso 使用
    """
    gdc = param
    OBJ = None
    if x.ndim == 1:
        Aw2 = gdc.Aw.copy().reshape(len(gdc.Aw), 1)
        for i in range(gdc.N_adjust):
            Aw2[gdc.index_w[i]] *= x[i]
        at_this = hecheng(gdc.t, gdc.w, gdc.fai, Aw2, gdc.bl)
        MAcc_this = resspec(gdc.t, at_this, gdc.Tyz, gdc.Damp)  # 生成反应谱
        sjp_this = gdc.sjp.copy().reshape(len(gdc.sjp), 1)
        error_this = (MAcc_this - sjp_this) / max(sjp_this)  # 计算曲线在各周期点误差
        OBJ = max(abs(error_this))  # 赋值给pop种群对象的ObjV属性  矩阵 每一行记录每一个个体的目标函数值
    elif x.ndim == 2:
        # 将Aw拓展为 len(Aw)* 种群个体数 的二维数组
        Aw2 = np.dot(gdc.Aw.copy().reshape(len(gdc.Aw), 1), np.ones((1, 5 * gdc.N_adjust)))
        for ii in range(gdc.N_adjust):  # 修正幅值系数
            Aw2[gdc.index_w[ii], :] *= x[:, ii]
        at_this = hecheng(gdc.t, gdc.w, gdc.fai, Aw2, gdc.bl)  # 合成二维曲线数组
        # 计算进化后的新个体对于曲线的改进效果
        MAcc_this = resspec(gdc.t, at_this, gdc.Tyz, gdc.Damp)  # 生成反应谱  大小： 反应谱点数*种群个体数
        this_sjp = np.dot(gdc.sjp.copy().reshape(len(gdc.sjp), 1), np.ones((1, 5 * gdc.N_adjust)))  # 对设计谱进行变形 拓宽
        error_this = (MAcc_this - this_sjp) / max(gdc.sjp)  # 计算各点误差
        # 赋值给pop种群对象的ObjV属性  矩阵 每一行记录每一个个体的目标函数值
        # 大小： 种群个体数*1
        OBJ = np.max(abs(error_this), axis=0).reshape(5 * gdc.N_adjust, 1)
    return OBJ


def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)  # 转换成C的long型数据
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)
